package com.caseeye.util
import org.springframework.util.ReflectionUtils

import scala.reflect.ClassManifest.ClassTypeManifest

import com.caseeye.model.annotation.IdField

import java.lang.reflect.Type
import java.util.Date

import java.lang.reflect.ParameterizedType
import com.caseeye.base.Entity
import java.lang.reflect.Method
import java.lang.reflect.Field
import org.springframework.util.{ ClassUtils => scu }

object ClassUtils {

    val entityClass = classOf[Entity]
    val entityListClass = classOf[List[Entity]]
    val entitySetClass = classOf[Set[Entity]]
    val dateClass = classOf[Date]
    val stringClass = classOf[String]

    private val persistableTypes = Set[Class[_]](entityClass, entityListClass, entitySetClass, dateClass, stringClass)

    def getParameterizedTypes(genericType : Type) : Array[Type] = {
        if (genericType.isInstanceOf[ParameterizedType]) {
            genericType.asInstanceOf[ParameterizedType].getActualTypeArguments
        } else {
            Array[Type]()
        }
    }

    def getParameterizedType(genericType : Type) : Type = {
        val types = getParameterizedTypes(genericType)
        if (types.length > 0) types(0) else null
    }

    def isPersistableType[T](t : Class[T]) : Boolean = {
        scu.isPrimitiveOrWrapper(t) ||
            scu.isPrimitiveArray(t) ||
            scu.isPrimitiveWrapperArray(t) ||
            !persistableTypes.filter(_ isAssignableFrom t).isEmpty
    }

    private val SetMethodCache = Map[String, Map[String, Method]]()
    private val GetMethodCache = Map[String, Map[String, Method]]()
    private val FieldCache = Map[String, Array[Field]]()

    private val setMethodR = """.*set([^\(]*).*"""
    private val getMethodR = """.*get([^\(]*).*"""

    def getSetMethods[T](clazz : Class[T]) : Map[String, Method] = {
        if (clazz == null) return Map[String, Method]()

        SetMethodCache.getOrElse(clazz.getName, {
            val re = Map[String, Method]() ++ clazz.getDeclaredMethods.filter(m => m.getName.matches(setMethodR)).map(m => { (m.getName -> m) })
            SetMethodCache + (clazz.getName -> re)
            re
        })
    }

    def getGetMethods[T](clazz : Class[T]) : Map[String, Method] = {
        if (clazz == null) return Map[String, Method]()

        GetMethodCache.getOrElse(clazz.getName, {
            val re = Map[String, Method]() ++ clazz.getDeclaredMethods.filter(m => m.getName.matches(getMethodR)).map(m => { (m.getName -> m) })
            GetMethodCache + (clazz.getName -> re)
            re
        })
    }

    def getGetMethod[T](clazz : Class[T], getMethodName : String) : Option[Method] = {
        if (clazz == null) return None

        getGetMethods(clazz).get(getMethodName)
    }

    def getSetMethod[T](clazz : Class[T], setMethodName : String) : Option[Method] = {
        if (clazz == null) return None

        getSetMethods(clazz).get(setMethodName)
    }

    def getFields[T](clazz : Class[T]) : Array[Field] = {
        if (clazz == null) return Array[Field]()

        FieldCache.getOrElse(clazz.getName, {
            val re = clazz.getDeclaredFields
            FieldCache + (clazz.getName -> re)
            re
        })
    }

    def getGetMethodName(fieldName : String) : String = {
        "get"+fieldName.head.toUpper + fieldName.tail
    }

    def getSetMethodName(fieldName : String) : String = {
        "set"+fieldName.head.toUpper + fieldName.tail
    }

    def bean2Map(bean : AnyRef) : Map[Field, AnyRef] = {
        Map[Field, AnyRef]() ++ getFields(bean.getClass).map(f => {
            val getMethod = getGetMethod(bean.getClass, getGetMethodName(f.getName))
            if (getMethod != None) (f -> getMethod.get.invoke(bean)) else (f -> None)
        })
    }

    def isIdField(field : Field) = field.getAnnotation(classOf[IdField]) != null

    def getIdField(clazz : Class[_]) : Option[Field] = getFields(clazz).find(field => isIdField(field))

    def getValueForField(entity : AnyRef, field : Field) : AnyRef = {
        ReflectionUtils.makeAccessible(field)

        ReflectionUtils.getField(field, entity)
    }

}